package com.yunxi.module.system.service.auth.impl;

import cn.hutool.core.util.ObjectUtil;
import com.google.common.annotations.VisibleForTesting;
import com.xingyuv.captcha.model.common.ResponseModel;
import com.xingyuv.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.service.CaptchaService;
import com.yunxi.framework.common.enums.CommonStatusEnum;
import com.yunxi.framework.common.enums.UserTypeEnum;
import com.yunxi.framework.common.util.monitor.TracerUtils;
import com.yunxi.framework.common.util.servlet.ServletUtils;
import com.yunxi.framework.common.util.validation.ValidationUtils;
import com.yunxi.module.system.convert.auth.AuthConvert;
import com.yunxi.module.system.dto.logger.LoginLogCreateReqDTO;
import com.yunxi.module.system.dto.social.SocialUserBindReqDTO;
import com.yunxi.module.system.enums.constant.ErrorCodeConstants;
import com.yunxi.module.system.enums.logger.LoginLogTypeEnum;
import com.yunxi.module.system.enums.logger.LoginResultEnum;
import com.yunxi.module.system.enums.oauth2.OAuth2ClientConstants;
import com.yunxi.module.system.model.oauth2.OAuth2AccessTokenDO;
import com.yunxi.module.system.model.user.AdminUserDO;
import com.yunxi.module.system.service.auth.AdminAuthService;
import com.yunxi.module.system.service.logger.LoginLogService;
import com.yunxi.module.system.service.member.MemberService;
import com.yunxi.module.system.service.oauth2.OAuth2TokenService;
import com.yunxi.module.system.service.social.SocialUserService;
import com.yunxi.module.system.service.user.AdminUserService;
import com.yunxi.module.system.vo.admin.auth.AuthLoginReqVO;
import com.yunxi.module.system.vo.admin.auth.AuthLoginRespVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.validation.Validator;
import java.util.Objects;

import static com.yunxi.framework.common.exception.util.ServiceExceptionUtil.exception;

/**
 * <p>
 * 管理后台的认证 Service 接口实现类
 * </p>
 *
 * @author lidy
 * @since 2024-05-08
 */
@Slf4j
@Service
public class AdminAuthServiceImpl implements AdminAuthService {

    @Resource
    private Validator validator;

    @Resource
    private CaptchaService captchaService;

    @Resource
    private LoginLogService loginLogService;

    @Resource
    private AdminUserService userService;

    @Resource
    private SocialUserService socialUserService;

    @Resource
    private OAuth2TokenService oAuth2TokenService;

    @Resource
    private MemberService memberService;

    /**
     * 验证码的开关，默认为 true
     */
    @Value("${yunxi.captcha.enable:true}")
    private Boolean captchaEnable;

    /**
     * 账号登录
     *
     * @param reqVO 登录信息
     * @return 登录结果
     */
    @Override
    public AuthLoginRespVO login(AuthLoginReqVO reqVO) {
        // 校验验证码
        validateCaptcha(reqVO);

        // 使用账号密码，进行登录
        AdminUserDO userDO = authenticate(reqVO.getUsername(), reqVO.getPassword());

        // 如果 socialType 非空，说明需要绑定社交用户
        if (reqVO.getSocialType() != null) {
            socialUserService.bindSocialUser(new SocialUserBindReqDTO(userDO.getId(), getUserType().getValue(),
                    reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
        }

        // 创建 Token 令牌，记录登录日志
        return createTokenAfterLoginSuccess(userDO.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
    }

    /**
     * 验证用户 账号 + 密码，如果通过，则返回用户信息
     *
     * @param username 用户账号
     * @param password 密码
     * @return 用户信息
     */
    @Override
    public AdminUserDO authenticate(String username, String password) {
        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
        // 校验账号是否存在
        AdminUserDO userDO = userService.getUserByUsername(username);

        if (userDO == null) {
            // 账号不存在
            createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS);
        }
        // 比对密码是否正确
        if (!userService.isPasswordMatch(password, userDO.getPassword())) {
            createLoginLog(userDO.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS);
        }
        // 校验用户是否禁用
        if (CommonStatusEnum.isDisable(userDO.getStatus())) {
            createLoginLog(userDO.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);
            throw exception(ErrorCodeConstants.AUTH_LOGIN_USER_DISABLED);
        }

        return userDO;
    }

    /**
     * 基于 token 退出登录
     *
     * @param token   token
     * @param logType 登出类型
     */
    @Override
    public void logout(String token, Integer logType) {
        // 删除访问令牌
        OAuth2AccessTokenDO accessTokenDO = oAuth2TokenService.removeAccessToken(token);
        if (accessTokenDO == null) {
            return;
        }
        // 删除成功，则记录登出日志
        createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
    }

    /**
     * 刷新访问令牌
     *
     * @param refreshToken 刷新令牌
     * @return 登录结果
     */
    @Override
    public AuthLoginRespVO refreshToken(String refreshToken) {
        OAuth2AccessTokenDO accessTokenDO = oAuth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
        return AuthConvert.INSTANCE.convert(accessTokenDO);
    }

    /**
     * 创建登出日志
     *
     * @param userId   用户ID
     * @param userType 用户类型
     * @param logType  日志类型
     */
    private void createLogoutLog(Long userId, Integer userType, Integer logType) {
        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
        reqDTO.setUserId(userId);
        reqDTO.setTraceId(TracerUtils.getTraceId());
        reqDTO.setLogType(logType);
        reqDTO.setUserAgent(ServletUtils.getUserAgent());
        reqDTO.setUserType(userType);
        reqDTO.setUserIp(ServletUtils.getClientIP());
        reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());

        if (ObjectUtil.equals(getUserType().getValue(), userType)) {
            reqDTO.setUsername(getUserName(userId));
        } else {
            reqDTO.setUsername(memberService.getMemberUserMobile(userId));
        }

        loginLogService.createLoginLog(reqDTO);
    }

    /**
     * 根据用户ID，获取用户账号
     *
     * @param userId 用户ID
     * @return 用户账号
     */
    private String getUserName(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserDO user = userService.getUser(userId);
        return user != null ? user.getUsername() : null;
    }

    /**
     * 创建 Token 令牌，记录登录日志
     *
     * @param userId   用户 ID
     * @param username 用户账号
     * @param logType  日志类型
     * @return Token 令牌
     */
    private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {
        // 插入登录日志
        createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
        // 创建访问令牌
        OAuth2AccessTokenDO accessTokenDO = oAuth2TokenService.createAccessToken(userId, getUserType().getValue(),
                OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
        // 构建返回结果
        return AuthConvert.INSTANCE.convert(accessTokenDO);
    }

    /**
     * 校验验证码
     *
     * @param reqVO 登录信息
     */
    @VisibleForTesting
    private void validateCaptcha(AuthLoginReqVO reqVO) {
        // 如果验证码关闭，则不进行校验
        if (!captchaEnable) {
            return;
        }
        // 校验验证码
        ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification());
        ResponseModel response = captchaService.verification(captchaVO);
        // 验证不通过
        if (!response.isSuccess()) {
            // 创建登录失败日志（验证码不正确）
            createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR);
            throw exception(ErrorCodeConstants.AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg());
        }
    }

    /**
     * 创建登录日志
     *
     * @param userId           用户ID
     * @param username         用户名
     * @param loginLogTypeEnum 用户类型枚举类
     * @param loginResultEnum  返回数据
     */
    private void createLoginLog(Long userId, String username, LoginLogTypeEnum loginLogTypeEnum, LoginResultEnum loginResultEnum) {
        // 插入登录日志
        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
        reqDTO.setLogType(loginLogTypeEnum.getType());
        reqDTO.setTraceId(TracerUtils.getTraceId());
        reqDTO.setUserId(userId);
        reqDTO.setUserType(getUserType().getValue());
        reqDTO.setUsername(username);
        reqDTO.setUserAgent(ServletUtils.getUserAgent());
        reqDTO.setUserIp(ServletUtils.getClientIP());
        reqDTO.setResult(loginResultEnum.getResult());

        loginLogService.createLoginLog(reqDTO);

        // 更新最后登录时间
        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResultEnum.getResult())) {
            userService.updateUserLogin(userId, ServletUtils.getClientIP());
        }


    }

    /**
     * 获取用户类型
     *
     * @return 用户类型
     */
    private UserTypeEnum getUserType() {
        return UserTypeEnum.ADMIN;
    }

}
